using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq.Expressions;
using System.Reflection;
using System.ServiceModel;
using System.Windows;
using System.Windows.Threading;
using OrbitOne.CRMLyncInfoDashboard.CRM.Organization;
using OrbitOne.CRMLyncInfoDashboard.CRM.WCF;

namespace OrbitOne.CRMLyncInfoDashboard.CRM.OData
{
    [Export(typeof(ICrmMetadataProvider))]
    public abstract class CrmMetadataProvider<T>: ICrmMetadataProvider where T : class
    {
        private readonly IWCFHelper m_wcfHelper;
        private readonly IClientScript m_clientScript;
        private Uri m_serverUrl;


        [ImportingConstructor]
        protected CrmMetadataProvider(IWCFHelper wcfHelper, IClientScript clientScript)
        {
            m_serverUrl = clientScript.GetServerUrl();
            m_wcfHelper = wcfHelper;
            m_clientScript = clientScript;
        }

        public void RetrieveEntity(IDataCollection viewModel, IEntityModel[] entities)
        {
            var client = (OrganizationServiceClient)m_wcfHelper.GetConnection(m_serverUrl);
                
            var entityLogicalName = typeof(T).Name.ToLower();

            var request = new OrganizationRequest
                              {
                                  RequestName = "RetrieveEntity",
                                  Parameters = new ParameterCollection
                                                   {
                                                       new WCF.KeyValuePair<string, object>("LogicalName", entityLogicalName),
                                                       new WCF.KeyValuePair<string, object>("EntityFilters", EntityFilters.Attributes),
                                                       new WCF.KeyValuePair<string, object>("MetadataId", Guid.Empty),
                                                       new WCF.KeyValuePair<string, object>("RetrieveAsIfPublished", true),
                                                   }
                              };
            client.ExecuteCompleted += (o, response) =>
                                           {
                                               var entityMetadataResult = GetEntityMetadataResult(response, viewModel);
                                               LinkEntitiesWithMetadata(entities, entityMetadataResult);
                                               var entityData = new EntityData(entities, entityMetadataResult);
                                               SetProperty(BindingTarget, viewModel, entityData);
                                               Deployment.Current.Dispatcher.BeginInvoke(() => viewModel.IsBusy = false);
                                           };
            client.ExecuteAsync(request);
        }

        private static void LinkEntitiesWithMetadata(IEnumerable<IEntityModel> entities, EntitytMetadataModel entityMetadataResult)
        {
            foreach (var entity in entities)
            {
                entity.Metadata = entityMetadataResult;
            }
        }

        private EntitytMetadataModel GetEntityMetadataResult(ExecuteCompletedEventArgs response, IDataCollection viewModel)
        {
            var metadata = (EntityMetadata)response.Result.Results[0].Value;
            return new EntitytMetadataModel(metadata);
        }

        public abstract Expression<Func<IDataCollection, object>> BindingTarget { get; }

        public static void SetProperty<TIn, TOut>(Expression<Func<TIn, TOut>> expression, object obj, object value)
        {
            try
            {
                Deployment.Current
                    .Dispatcher
                    .BeginInvoke(() =>
                                 ((PropertyInfo) (GetMemberExpression(expression).Member)).SetValue(obj, value, null));

            }
            catch (Exception ex)
            {
                throw new Exception(expression.ToString(), ex);
            }
        }

        private static MemberExpression GetMemberExpression<TIn, TOut>(Expression<Func<TIn, TOut>> expression)
        {
            switch (expression.Body.NodeType)
            {
                case ExpressionType.MemberAccess:
                    return ((MemberExpression)(expression.Body));

                case ExpressionType.Call:
                    return ((MemberExpression)(((MethodCallExpression)(expression.Body)).Object));

                default:
                    return ((MemberExpression)(((UnaryExpression)(expression.Body)).Operand));
            }
        }

        
    }
}